/*
 * Copyright (c) 2021, Meco Jianting Man <jiantingman@foxmail.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-11-27     Meco Man     porting for rt_vsnprintf as the fully functional
 * version
 */

/**
 * @author (c) Eyal Rozenberg <eyalroz1@gmx.com>
 *             2021, Haifa, Palestine/Israel
 * @author (c) Marco Paland (info@paland.com)
 *             2014-2019, PALANDesign Hannover, Germany
 *
 * @note Others have made smaller contributions to this file: see the
 * contributors page at https://github.com/eyalroz/printf/graphs/contributors
 * or ask one of the authors.
 *
 * @brief Small stand-alone implementation of the printf family of functions
 * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems
 * with a very limited resources.
 *
 * @note the implementations are thread-safe; re-entrant; use no functions from
 * the standard library; and do not dynamically allocate any memory.
 *
 * @license The MIT License (MIT)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "PikaObj.h"
#include "pika_adapter_rtt.h"

#if !PIKASCRIPT_VERSION_REQUIRE_MINIMUN(1, 12, 0)
#error "pika_vsnprintf.c requires at least PikaScript 1.12.0"
#endif

// 'ntoa' conversion buffer size, this must be big enough to hold one converted
// numeric number including padded zeros (dynamically created on stack)
#ifndef PRINTF_INTEGER_BUFFER_SIZE
#define PRINTF_INTEGER_BUFFER_SIZE 32
#endif

// 'ftoa' conversion buffer size, this must be big enough to hold one converted
// float number including padded zeros (dynamically created on stack)
#ifndef PRINTF_FTOA_BUFFER_SIZE
#define PRINTF_FTOA_BUFFER_SIZE 32
#endif

// Support for the decimal notation floating point conversion specifiers (%f,
// %F)
#ifndef PRINTF_SUPPORT_DECIMAL_SPECIFIERS
#define PRINTF_SUPPORT_DECIMAL_SPECIFIERS 1
#endif

// Support for the exponential notatin floating point conversion specifiers (%e,
// %g, %E, %G)
#ifndef PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
#define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 1
#endif

// Default precision for the floating point conversion specifiers (the C
// standard sets this at 6)
#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
#define PRINTF_DEFAULT_FLOAT_PRECISION 6
#endif

// According to the C languages standard, printf() and related functions must be
// able to print any integral number in floating-point notation, regardless of
// length, when using the %f specifier - possibly hundreds of characters,
// potentially overflowing your buffers. In this implementation, all values
// beyond this threshold are switched to exponential notation.
#ifndef PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL
#define PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL 9
#endif

// Support for the long long integral types (with the ll, z and t length
// modifiers for specifiers %d,%i,%o,%x,%X,%u, and with the %p specifier). Note:
// 'L' (long double) is not supported.
#ifndef PRINTF_SUPPORT_LONG_LONG
#define PRINTF_SUPPORT_LONG_LONG 1
#endif

#if PRINTF_SUPPORT_LONG_LONG
typedef unsigned long long printf_unsigned_value_t;
typedef long long printf_signed_value_t;
#else
typedef unsigned long printf_unsigned_value_t;
typedef long printf_signed_value_t;
#endif

#define PRINTF_PREFER_DECIMAL false
#define PRINTF_PREFER_EXPONENTIAL true

///////////////////////////////////////////////////////////////////////////////

// The following will convert the number-of-digits into an exponential-notation
// literal
#define PRINTF_CONCATENATE(s1, s2) s1##s2
#define PRINTF_EXPAND_THEN_CONCATENATE(s1, s2) PRINTF_CONCATENATE(s1, s2)
#define PRINTF_FLOAT_NOTATION_THRESHOLD \
    PRINTF_EXPAND_THEN_CONCATENATE(1e, PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL)

// internal flag definitions
#define FLAGS_ZEROPAD (1U << 0U)
#define FLAGS_LEFT (1U << 1U)
#define FLAGS_PLUS (1U << 2U)
#define FLAGS_SPACE (1U << 3U)
#define FLAGS_HASH (1U << 4U)
#define FLAGS_UPPERCASE (1U << 5U)
#define FLAGS_CHAR (1U << 6U)
#define FLAGS_SHORT (1U << 7U)
#define FLAGS_LONG (1U << 8U)
#define FLAGS_LONG_LONG (1U << 9U)
#define FLAGS_PRECISION (1U << 10U)
#define FLAGS_ADAPT_EXP (1U << 11U)
#define FLAGS_POINTER (1U << 12U)
// Note: Similar, but not identical, effect as FLAGS_HASH

#define BASE_BINARY 2
#define BASE_OCTAL 8
#define BASE_DECIMAL 10
#define BASE_HEX 16

typedef uint8_t numeric_base_t;

#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)
#include <float.h>
#if FLT_RADIX != 2
#error "Non-binary-radix floating-point types are unsupported."
#endif

#if DBL_MANT_DIG == 24

#define DOUBLE_SIZE_IN_BITS 32
typedef uint32_t double_uint_t;
#define DOUBLE_EXPONENT_MASK 0xFFU
#define DOUBLE_BASE_EXPONENT 127

#elif DBL_MANT_DIG == 53

#define DOUBLE_SIZE_IN_BITS 64
typedef uint64_t double_uint_t;
#define DOUBLE_EXPONENT_MASK 0x7FFU
#define DOUBLE_BASE_EXPONENT 1023

#else
#error "Unsupported double type configuration"
#endif
#define DOUBLE_STORED_MANTISSA_BITS (DBL_MANT_DIG - 1)

typedef union {
    double_uint_t U;
    double F;
} double_with_bit_access;

// This is unnecessary in C99, since compound initializers can be used,
// but: 1. Some compilers are finicky about this; 2. Some people may want to
// convert this to C89;
// 3. If you try to use it as C++, only C++20 supports compound literals
static inline double_with_bit_access get_bit_access(double x) {
    double_with_bit_access dwba;
    dwba.F = x;
    return dwba;
}

static inline int get_sign(double x) {
    // The sign is stored in the highest bit
    return get_bit_access(x).U >> (DOUBLE_SIZE_IN_BITS - 1);
}

static inline int get_exp2(double_with_bit_access x) {
    // The exponent in an IEEE-754 floating-point number occupies a contiguous
    // sequence of bits (e.g. 52..62 for 64-bit doubles), but with a non-trivial
    // representation: An unsigned offset from some negative value (with the
    // extremal offset values reserved for special use).
    return (int)((x.U >> DOUBLE_STORED_MANTISSA_BITS) & DOUBLE_EXPONENT_MASK) -
           DOUBLE_BASE_EXPONENT;
}
#define PRINTF_ABS(_x) ((_x) > 0 ? (_x) : -(_x))

#endif  // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS ||
        // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)

// Note in particular the behavior here on LONG_MIN or LLONG_MIN; it is valid
// and well-defined, but if you're not careful you can easily trigger undefined
// behavior with -LONG_MIN or -LLONG_MIN
#define ABS_FOR_PRINTING(_x) \
    ((printf_unsigned_value_t)((_x) > 0 ? (_x) : -((printf_signed_value_t)_x)))

// output function type
typedef void (*out_fct_type)(char character,
                             void* buffer,
                             size_t idx,
                             size_t maxlen);

// wrapper (used as buffer) for output function type
typedef struct {
    void (*fct)(char character, void* arg);
    void* arg;
} out_function_wrapper_type;

// internal buffer output
static inline void out_buffer(char character,
                              void* buffer,
                              size_t idx,
                              size_t maxlen) {
    if (idx < maxlen) {
        ((char*)buffer)[idx] = character;
    }
}

// internal null output
static inline void out_discard(char character,
                               void* buffer,
                               size_t idx,
                               size_t maxlen) {
    (void)character;
    (void)buffer;
    (void)idx;
    (void)maxlen;
}

// internal secure strlen
// @return The length of the string (excluding the terminating 0) limited by
// 'maxsize'
static inline unsigned int strnlen_s_(const char* str, size_t maxsize) {
    const char* s;
    for (s = str; *s && maxsize--; ++s)
        ;
    return (unsigned int)(s - str);
}

// internal test if char is a digit (0-9)
// @return true if char is a digit
static inline bool is_digit_(char ch) {
    return (ch >= '0') && (ch <= '9');
}

// internal ASCII string to unsigned int conversion
static unsigned int atoi_(const char** str) {
    unsigned int i = 0U;
    while (is_digit_(**str)) {
        i = i * 10U + (unsigned int)(*((*str)++) - '0');
    }
    return i;
}

// output the specified string in reverse, taking care of any zero-padding
static size_t out_rev_(out_fct_type out,
                       char* buffer,
                       size_t idx,
                       size_t maxlen,
                       const char* buf,
                       size_t len,
                       unsigned int width,
                       unsigned int flags) {
    const size_t start_idx = idx;

    // pad spaces up to given width
    if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
        for (size_t i = len; i < width; i++) {
            out(' ', buffer, idx++, maxlen);
        }
    }

    // reverse string
    while (len) {
        out(buf[--len], buffer, idx++, maxlen);
    }

    // append pad spaces up to given width
    if (flags & FLAGS_LEFT) {
        while (idx - start_idx < width) {
            out(' ', buffer, idx++, maxlen);
        }
    }

    return idx;
}

// Invoked by print_integer after the actual number has been printed, performing
// necessary work on the number's prefix (as the number is initially printed in
// reverse order)
static size_t print_integer_finalization(out_fct_type out,
                                         char* buffer,
                                         size_t idx,
                                         size_t maxlen,
                                         char* buf,
                                         size_t len,
                                         bool negative,
                                         numeric_base_t base,
                                         unsigned int precision,
                                         unsigned int width,
                                         unsigned int flags) {
    size_t unpadded_len = len;

    // pad with leading zeros
    {
        if (!(flags & FLAGS_LEFT)) {
            if (width && (flags & FLAGS_ZEROPAD) &&
                (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
                width--;
            }
            while ((flags & FLAGS_ZEROPAD) && (len < width) &&
                   (len < PRINTF_INTEGER_BUFFER_SIZE)) {
                buf[len++] = '0';
            }
        }

        while ((len < precision) && (len < PRINTF_INTEGER_BUFFER_SIZE)) {
            buf[len++] = '0';
        }

        if (base == BASE_OCTAL && (len > unpadded_len)) {
            // Since we've written some zeros, we've satisfied the alternative
            // format leading space requirement
            flags &= ~FLAGS_HASH;
        }
    }

    // handle hash
    if (flags & (FLAGS_HASH | FLAGS_POINTER)) {
        if (!(flags & FLAGS_PRECISION) && len &&
            ((len == precision) || (len == width))) {
            // Let's take back some padding digits to fit in what will
            // eventually be the format-specific prefix
            if (unpadded_len < len) {
                len--;
            }
            if (len && (base == BASE_HEX)) {
                if (unpadded_len < len) {
                    len--;
                }
            }
        }
        if ((base == BASE_HEX) && !(flags & FLAGS_UPPERCASE) &&
            (len < PRINTF_INTEGER_BUFFER_SIZE)) {
            buf[len++] = 'x';
        } else if ((base == BASE_HEX) && (flags & FLAGS_UPPERCASE) &&
                   (len < PRINTF_INTEGER_BUFFER_SIZE)) {
            buf[len++] = 'X';
        } else if ((base == BASE_BINARY) &&
                   (len < PRINTF_INTEGER_BUFFER_SIZE)) {
            buf[len++] = 'b';
        }
        if (len < PRINTF_INTEGER_BUFFER_SIZE) {
            buf[len++] = '0';
        }
    }

    if (len < PRINTF_INTEGER_BUFFER_SIZE) {
        if (negative) {
            buf[len++] = '-';
        } else if (flags & FLAGS_PLUS) {
            buf[len++] = '+';  // ignore the space if the '+' exists
        } else if (flags & FLAGS_SPACE) {
            buf[len++] = ' ';
        }
    }

    return out_rev_(out, buffer, idx, maxlen, buf, len, width, flags);
}

// An internal itoa-like function
static size_t print_integer(out_fct_type out,
                            char* buffer,
                            size_t idx,
                            size_t maxlen,
                            printf_unsigned_value_t value,
                            bool negative,
                            numeric_base_t base,
                            unsigned int precision,
                            unsigned int width,
                            unsigned int flags) {
    char buf[PRINTF_INTEGER_BUFFER_SIZE];
    size_t len = 0U;

    if (!value) {
        if (!(flags & FLAGS_PRECISION)) {
            buf[len++] = '0';
            flags &= ~FLAGS_HASH;
            // We drop this flag this since either the alternative and regular
            // modes of the specifier don't differ on 0 values, or (in the case
            // of octal) we've already provided the special handling for this
            // mode.
        } else if (base == BASE_HEX) {
            flags &= ~FLAGS_HASH;
            // We drop this flag this since either the alternative and regular
            // modes of the specifier don't differ on 0 values
        }
    } else {
        do {
            const char digit = (char)(value % base);
            buf[len++] =
                (char)(digit < 10 ? '0' + digit
                                  : (flags & FLAGS_UPPERCASE ? 'A' : 'a') +
                                        digit - 10);
            value /= base;
        } while (value && (len < PRINTF_INTEGER_BUFFER_SIZE));
    }

    return print_integer_finalization(out, buffer, idx, maxlen, buf, len,
                                      negative, base, precision, width, flags);
}

#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)

struct double_components {
    int_fast64_t integral;
    int_fast64_t fractional;
    bool is_negative;
};

#define NUM_DECIMAL_DIGITS_IN_INT64_T 18
#define PRINTF_MAX_PRECOMPUTED_POWER_OF_10 NUM_DECIMAL_DIGITS_IN_INT64_T
static const double powers_of_10[NUM_DECIMAL_DIGITS_IN_INT64_T] = {
    1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08,
    1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17};

#define PRINTF_MAX_SUPPORTED_PRECISION NUM_DECIMAL_DIGITS_IN_INT64_T - 1

// Break up a double number - which is known to be a finite non-negative number
// - into its base-10 parts: integral - before the decimal point, and fractional
// - after it. Taken the precision into account, but does not change it even
// internally.
static struct double_components get_components(double number,
                                               unsigned int precision) {
    struct double_components number_;
    number_.is_negative = get_sign(number);
    double abs_number = (number_.is_negative) ? -number : number;
    number_.integral = (int_fast64_t)abs_number;
    double remainder =
        (abs_number - number_.integral) * powers_of_10[precision];
    number_.fractional = (int_fast64_t)remainder;

    remainder -= (double)number_.fractional;

    if (remainder > 0.5) {
        ++number_.fractional;
        // handle rollover, e.g. case 0.99 with precision 1 is 1.0
        if ((double)number_.fractional >= powers_of_10[precision]) {
            number_.fractional = 0;
            ++number_.integral;
        }
    } else if (remainder == 0.5) {
        if ((number_.fractional == 0U) || (number_.fractional & 1U)) {
            // if halfway, round up if odd OR if last digit is 0
            ++number_.fractional;
        }
    }

    if (precision == 0U) {
        remainder = abs_number - (double)number_.integral;
        if ((!(remainder < 0.5) || (remainder > 0.5)) &&
            (number_.integral & 1)) {
            // exactly 0.5 and ODD, then round up
            // 1.5 -> 2, but 2.5 -> 2
            ++number_.integral;
        }
    }
    return number_;
}

struct scaling_factor {
    double raw_factor;
    bool multiply;  // if true, need to multiply by raw_factor; otherwise need
                    // to divide by it
};

double apply_scaling(double num, struct scaling_factor normalization) {
    return normalization.multiply ? num * normalization.raw_factor
                                  : num / normalization.raw_factor;
}

double unapply_scaling(double normalized, struct scaling_factor normalization) {
    return normalization.multiply ? normalized / normalization.raw_factor
                                  : normalized * normalization.raw_factor;
}

struct scaling_factor update_normalization(struct scaling_factor sf,
                                           double extra_multiplicative_factor) {
    struct scaling_factor result;
    if (sf.multiply) {
        result.multiply = true;
        result.raw_factor = sf.raw_factor * extra_multiplicative_factor;
    } else {
        int factor_exp2 = get_exp2(get_bit_access(sf.raw_factor));
        int extra_factor_exp2 =
            get_exp2(get_bit_access(extra_multiplicative_factor));

        // Divide the larger-exponent raw raw_factor by the smaller
        if (PRINTF_ABS(factor_exp2) > PRINTF_ABS(extra_factor_exp2)) {
            result.multiply = false;
            result.raw_factor = sf.raw_factor / extra_multiplicative_factor;
        } else {
            result.multiply = true;
            result.raw_factor = extra_multiplicative_factor / sf.raw_factor;
        }
    }
    return result;
}

#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
static struct double_components get_normalized_components(
    bool negative,
    unsigned int precision,
    double non_normalized,
    struct scaling_factor normalization) {
    struct double_components components;
    components.is_negative = negative;
    components.integral =
        (int_fast64_t)apply_scaling(non_normalized, normalization);
    double remainder =
        non_normalized -
        unapply_scaling((double)components.integral, normalization);
    double prec_power_of_10 = powers_of_10[precision];
    struct scaling_factor account_for_precision =
        update_normalization(normalization, prec_power_of_10);
    double scaled_remainder = apply_scaling(remainder, account_for_precision);
    double rounding_threshold = 0.5;

    if (precision == 0U) {
        components.fractional = 0;
        components.integral += (scaled_remainder >= rounding_threshold);
        if (scaled_remainder == rounding_threshold) {
            // banker's rounding: Round towards the even number (making the mean
            // error 0)
            components.integral &= ~((int_fast64_t)0x1);
        }
    } else {
        components.fractional = (int_fast64_t)scaled_remainder;
        scaled_remainder -= components.fractional;

        components.fractional += (scaled_remainder >= rounding_threshold);
        if (scaled_remainder == rounding_threshold) {
            // banker's rounding: Round towards the even number (making the mean
            // error 0)
            components.fractional &= ~((int_fast64_t)0x1);
        }
        // handle rollover, e.g. the case of 0.99 with precision 1 becoming
        // (0,100), and must then be corrected into (1, 0).
        if ((double)components.fractional >= prec_power_of_10) {
            components.fractional = 0;
            ++components.integral;
        }
    }
    return components;
}
#endif

static size_t print_broken_up_decimal(struct double_components number_,
                                      out_fct_type out,
                                      char* buffer,
                                      size_t idx,
                                      size_t maxlen,
                                      unsigned int precision,
                                      unsigned int width,
                                      unsigned int flags,
                                      char* buf,
                                      size_t len) {
    if (precision != 0U) {
        // do fractional part, as an unsigned number

        unsigned int count = precision;

        if (flags & FLAGS_ADAPT_EXP && !(flags & FLAGS_HASH)) {
            // %g/%G mandates we skip the trailing 0 digits...
            if (number_.fractional > 0) {
                while (true) {
                    int_fast64_t digit = number_.fractional % 10U;
                    if (digit != 0) {
                        break;
                    }
                    --count;
                    number_.fractional /= 10U;
                }
            }
            // ... and even the decimal point if there are no
            // non-zero fractional part digits (see below)
        }

        if (number_.fractional > 0 || !(flags & FLAGS_ADAPT_EXP) ||
            (flags & FLAGS_HASH)) {
            while (len < PRINTF_FTOA_BUFFER_SIZE) {
                --count;
                buf[len++] = (char)('0' + number_.fractional % 10U);
                if (!(number_.fractional /= 10U)) {
                    break;
                }
            }
            // add extra 0s
            while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
                buf[len++] = '0';
            }
            if (len < PRINTF_FTOA_BUFFER_SIZE) {
                buf[len++] = '.';
            }
        }
    } else {
        if (flags & FLAGS_HASH) {
            if (len < PRINTF_FTOA_BUFFER_SIZE) {
                buf[len++] = '.';
            }
        }
    }

    // Write the integer part of the number (it comes after the fractional
    // since the character order is reversed)
    while (len < PRINTF_FTOA_BUFFER_SIZE) {
        buf[len++] = (char)('0' + (number_.integral % 10));
        if (!(number_.integral /= 10)) {
            break;
        }
    }

    // pad leading zeros
    if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
        if (width &&
            (number_.is_negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
            width--;
        }
        while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
            buf[len++] = '0';
        }
    }

    if (len < PRINTF_FTOA_BUFFER_SIZE) {
        if (number_.is_negative) {
            buf[len++] = '-';
        } else if (flags & FLAGS_PLUS) {
            buf[len++] = '+';  // ignore the space if the '+' exists
        } else if (flags & FLAGS_SPACE) {
            buf[len++] = ' ';
        }
    }

    return out_rev_(out, buffer, idx, maxlen, buf, len, width, flags);
}

// internal ftoa for fixed decimal floating point
static size_t print_decimal_number(out_fct_type out,
                                   char* buffer,
                                   size_t idx,
                                   size_t maxlen,
                                   double number,
                                   unsigned int precision,
                                   unsigned int width,
                                   unsigned int flags,
                                   char* buf,
                                   size_t len) {
    struct double_components value_ = get_components(number, precision);
    return print_broken_up_decimal(value_, out, buffer, idx, maxlen, precision,
                                   width, flags, buf, len);
}

#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
// internal ftoa variant for exponential floating-point type, contributed by
// Martijn Jasperse <m.jasperse@gmail.com>
static size_t print_exponential_number(out_fct_type out,
                                       char* buffer,
                                       size_t idx,
                                       size_t maxlen,
                                       double number,
                                       unsigned int precision,
                                       unsigned int width,
                                       unsigned int flags,
                                       char* buf,
                                       size_t len) {
    const bool negative = get_sign(number);
    // This number will decrease gradually (by factors of 10) as we "extract"
    // the exponent out of it
    double abs_number = negative ? -number : number;

    int exp10;
    bool abs_exp10_covered_by_powers_table;
    struct scaling_factor normalization;

    // Determine the decimal exponent
    if (abs_number == 0.0) {
        // TODO: This is a special-case for 0.0 (and -0.0); but proper handling
        // is required for denormals more generally.
        exp10 = 0;  // ... and no need to set a normalization factor or check
                    // the powers table
    } else {
        double_with_bit_access conv = get_bit_access(abs_number);
        {
            // based on the algorithm by David Gay
            // (https://www.ampl.com/netlib/fp/dtoa.c)
            int exp2 = get_exp2(conv);
            // drop the exponent, so conv.F comes into the range [1,2)
            conv.U =
                (conv.U &
                 (((double_uint_t)(1) << DOUBLE_STORED_MANTISSA_BITS) - 1U)) |
                ((double_uint_t)DOUBLE_BASE_EXPONENT
                 << DOUBLE_STORED_MANTISSA_BITS);
            // now approximate log10 from the log2 integer part and an expansion
            // of ln around 1.5
            exp10 = (int)(0.1760912590558 + exp2 * 0.301029995663981 +
                          (conv.F - 1.5) * 0.289529654602168);
            // now we want to compute 10^exp10 but we want to be sure it won't
            // overflow
            exp2 = (int)(exp10 * 3.321928094887362 + 0.5);
            const double z =
                exp10 * 2.302585092994046 - exp2 * 0.6931471805599453;
            const double z2 = z * z;
            conv.U = ((double_uint_t)(exp2) + DOUBLE_BASE_EXPONENT)
                     << DOUBLE_STORED_MANTISSA_BITS;
            // compute exp(z) using continued fractions, see
            // https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
            conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
            // correct for rounding errors
            if (abs_number < conv.F) {
                exp10--;
                conv.F /= 10;
            }
        }
        abs_exp10_covered_by_powers_table =
            PRINTF_ABS(exp10) < PRINTF_MAX_PRECOMPUTED_POWER_OF_10;
        normalization.raw_factor = abs_exp10_covered_by_powers_table
                                       ? powers_of_10[PRINTF_ABS(exp10)]
                                       : conv.F;
    }

    // We now begin accounting for the widths of the two parts of our printed
    // field: the decimal part after decimal exponent extraction, and the
    // base-10 exponent part. For both of these, the value of 0 has a special
    // meaning, but not the same one: a 0 exponent-part width means "don't print
    // the exponent"; a 0 decimal-part width means "use as many characters as
    // necessary".

    bool fall_back_to_decimal_only_mode = false;
    if (flags & FLAGS_ADAPT_EXP) {
        int required_significant_digits = (precision == 0) ? 1 : (int)precision;
        // Should we want to fall-back to "%f" mode, and only print the decimal
        // part?
        fall_back_to_decimal_only_mode =
            (exp10 >= -4 && exp10 < required_significant_digits);
        // Now, let's adjust the precision
        // This also decided how we adjust the precision value - as in "%g"
        // mode, "precision" is the number of _significant digits_, and this is
        // when we "translate" the precision value to an actual number of
        // decimal digits.
        int precision_ =
            (fall_back_to_decimal_only_mode)
                ? (int)precision - 1 - exp10
                : (int)precision -
                      1;  // the presence of the exponent ensures only one
                          // significant digit comes before the decimal point
        precision = (precision_ > 0 ? (unsigned)precision_ : 0U);
        flags |= FLAGS_PRECISION;  // make sure print_broken_up_decimal respects
                                   // our choice above
    }

    normalization.multiply = (exp10 < 0 && abs_exp10_covered_by_powers_table);
    bool should_skip_normalization =
        (fall_back_to_decimal_only_mode || exp10 == 0);
    struct double_components decimal_part_components =
        should_skip_normalization
            ? get_components(negative ? -abs_number : abs_number, precision)
            : get_normalized_components(negative, precision, abs_number,
                                        normalization);

    // Account for roll-over, e.g. rounding from 9.99 to 100.0 - which effects
    // the exponent and may require additional tweaking of the parts
    if (fall_back_to_decimal_only_mode) {
        if ((flags & FLAGS_ADAPT_EXP) && exp10 >= -1 &&
            decimal_part_components.integral == powers_of_10[exp10 + 1]) {
            exp10++;  // Not strictly necessary, since exp10 is no longer really
                      // used
            precision--;
            // ... and it should already be the case that
            // decimal_part_components.fractional == 0
        }
        // TODO: What about rollover strictly within the fractional part?
    } else {
        if (decimal_part_components.integral >= 10) {
            exp10++;
            decimal_part_components.integral = 1;
            decimal_part_components.fractional = 0;
        }
    }

    // the exp10 format is "E%+03d" and largest possible exp10 value for a
    // 64-bit double is "307" (for 2^1023), so we set aside 4-5 characters
    // overall
    unsigned int exp10_part_width = fall_back_to_decimal_only_mode ? 0U
                                    : (PRINTF_ABS(exp10) < 100)    ? 4U
                                                                   : 5U;

    unsigned int decimal_part_width =
        ((flags & FLAGS_LEFT) && exp10_part_width)
            ?
            // We're padding on the right, so the width constraint is the
            // exponent part's problem, not the decimal part's, so we'll use as
            // many characters as we need:
            0U
            :
            // We're padding on the left; so the width constraint is the decimal
            // part's problem. Well, can both the decimal part and the exponent
            // part fit within our overall width?
            ((width > exp10_part_width)
                 ?
                 // Yes, so we limit our decimal part's width.
                 // (Note this is trivially valid even if we've fallen back to
                 // "%f" mode)
                 width - exp10_part_width
                 :
                 // No; we just give up on any restriction on the decimal part
                 // and use as many characters as we need
                 0U);

    const size_t start_idx = idx;
    idx = print_broken_up_decimal(decimal_part_components, out, buffer, idx,
                                  maxlen, precision, decimal_part_width, flags,
                                  buf, len);

    if (!fall_back_to_decimal_only_mode) {
        out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
        idx = print_integer(out, buffer, idx, maxlen, ABS_FOR_PRINTING(exp10),
                            exp10 < 0, 10, 0, exp10_part_width - 1,
                            FLAGS_ZEROPAD | FLAGS_PLUS);
        if (flags & FLAGS_LEFT) {
            // We need to right-pad with spaces to meet the width requirement
            while (idx - start_idx < width)
                out(' ', buffer, idx++, maxlen);
        }
    }
    return idx;
}
#endif  // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS

static size_t print_floating_point(out_fct_type out,
                                   char* buffer,
                                   size_t idx,
                                   size_t maxlen,
                                   double value,
                                   unsigned int precision,
                                   unsigned int width,
                                   unsigned int flags,
                                   bool prefer_exponential) {
    char buf[PRINTF_FTOA_BUFFER_SIZE];
    size_t len = 0U;

    // test for special values
    if (value != value)
        return out_rev_(out, buffer, idx, maxlen, "nan", 3, width, flags);
    if (value < -DBL_MAX)
        return out_rev_(out, buffer, idx, maxlen, "fni-", 4, width, flags);
    if (value > DBL_MAX)
        return out_rev_(out, buffer, idx, maxlen,
                        (flags & FLAGS_PLUS) ? "fni+" : "fni",
                        (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);

    if (!prefer_exponential && ((value > PRINTF_FLOAT_NOTATION_THRESHOLD) ||
                                (value < -PRINTF_FLOAT_NOTATION_THRESHOLD))) {
        // The required behavior of standard printf is to print _every_
        // integral-part digit -- which could mean printing hundreds of
        // characters, overflowing any fixed internal buffer and necessitating a
        // more complicated implementation.
#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
        return print_exponential_number(out, buffer, idx, maxlen, value,
                                        precision, width, flags, buf, len);
#else
        return 0U;
#endif
    }

    // set default precision, if not set explicitly
    if (!(flags & FLAGS_PRECISION)) {
        precision = PRINTF_DEFAULT_FLOAT_PRECISION;
    }

    // limit precision so that our integer holding the fractional part does not
    // overflow
    while ((len < PRINTF_FTOA_BUFFER_SIZE) &&
           (precision > PRINTF_MAX_SUPPORTED_PRECISION)) {
        buf[len++] =
            '0';  // This respects the precision in terms of result length only
        precision--;
    }

    return
#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
        prefer_exponential
            ? print_exponential_number(out, buffer, idx, maxlen, value,
                                       precision, width, flags, buf, len)
            :
#endif
            print_decimal_number(out, buffer, idx, maxlen, value, precision,
                                 width, flags, buf, len);
}

#endif  // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS ||
        // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS)

// internal vsnprintf
static int __vsnprintf(out_fct_type out,
                       char* buffer,
                       const size_t maxlen,
                       const char* format,
                       va_list va) {
    unsigned int flags, width, precision, n;
    size_t idx = 0U;

    if (!buffer) {
        // use null output function
        out = out_discard;
    }

    while (*format) {
        // format specifier?  %[flags][width][.precision][length]
        if (*format != '%') {
            // no
            out(*format, buffer, idx++, maxlen);
            format++;
            continue;
        } else {
            // yes, evaluate it
            format++;
        }

        // evaluate flags
        flags = 0U;
        do {
            switch (*format) {
                case '0':
                    flags |= FLAGS_ZEROPAD;
                    format++;
                    n = 1U;
                    break;
                case '-':
                    flags |= FLAGS_LEFT;
                    format++;
                    n = 1U;
                    break;
                case '+':
                    flags |= FLAGS_PLUS;
                    format++;
                    n = 1U;
                    break;
                case ' ':
                    flags |= FLAGS_SPACE;
                    format++;
                    n = 1U;
                    break;
                case '#':
                    flags |= FLAGS_HASH;
                    format++;
                    n = 1U;
                    break;
                default:
                    n = 0U;
                    break;
            }
        } while (n);

        // evaluate width field
        width = 0U;
        if (is_digit_(*format)) {
            width = atoi_(&format);
        } else if (*format == '*') {
            const int w = va_arg(va, int);
            if (w < 0) {
                flags |= FLAGS_LEFT;  // reverse padding
                width = (unsigned int)-w;
            } else {
                width = (unsigned int)w;
            }
            format++;
        }

        // evaluate precision field
        precision = 0U;
        if (*format == '.') {
            flags |= FLAGS_PRECISION;
            format++;
            if (is_digit_(*format)) {
                precision = atoi_(&format);
            } else if (*format == '*') {
                const int precision_ = (int)va_arg(va, int);
                precision = precision_ > 0 ? (unsigned int)precision_ : 0U;
                format++;
            }
        }

        // evaluate length field
        switch (*format) {
            case 'l':
                flags |= FLAGS_LONG;
                format++;
                if (*format == 'l') {
                    flags |= FLAGS_LONG_LONG;
                    format++;
                }
                break;
            case 'h':
                flags |= FLAGS_SHORT;
                format++;
                if (*format == 'h') {
                    flags |= FLAGS_CHAR;
                    format++;
                }
                break;
            case 't':
                flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG
                                                            : FLAGS_LONG_LONG);
                format++;
                break;
            case 'j':
                flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG
                                                           : FLAGS_LONG_LONG);
                format++;
                break;
            case 'z':
                flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG
                                                         : FLAGS_LONG_LONG);
                format++;
                break;
            default:
                break;
        }

        // evaluate specifier
        switch (*format) {
            case 'd':
            case 'i':
            case 'u':
            case 'x':
            case 'X':
            case 'o':
            case 'b': {
                // set the base
                numeric_base_t base;
                if (*format == 'x' || *format == 'X') {
                    base = BASE_HEX;
                } else if (*format == 'o') {
                    base = BASE_OCTAL;
                } else if (*format == 'b') {
                    base = BASE_BINARY;
                } else {
                    base = BASE_DECIMAL;
                    flags &= ~FLAGS_HASH;  // no hash for dec format
                }
                // uppercase
                if (*format == 'X') {
                    flags |= FLAGS_UPPERCASE;
                }

                // no plus or space flag for u, x, X, o, b
                if ((*format != 'i') && (*format != 'd')) {
                    flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
                }

                // ignore '0' flag when precision is given
                if (flags & FLAGS_PRECISION) {
                    flags &= ~FLAGS_ZEROPAD;
                }

                // convert the integer
                if ((*format == 'i') || (*format == 'd')) {
                    // signed
                    if (flags & FLAGS_LONG_LONG) {
#if PRINTF_SUPPORT_LONG_LONG
                        const long long value = va_arg(va, long long);
                        idx = print_integer(out, buffer, idx, maxlen,
                                            ABS_FOR_PRINTING(value), value < 0,
                                            base, precision, width, flags);
#endif
                    } else if (flags & FLAGS_LONG) {
                        const long value = va_arg(va, long);
                        idx = print_integer(out, buffer, idx, maxlen,
                                            ABS_FOR_PRINTING(value), value < 0,
                                            base, precision, width, flags);
                    } else {
                        const int value =
                            (flags & FLAGS_CHAR) ? (signed char)va_arg(va, int)
                            : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int)
                                                    : va_arg(va, int);
                        idx = print_integer(out, buffer, idx, maxlen,
                                            ABS_FOR_PRINTING(value), value < 0,
                                            base, precision, width, flags);
                    }
                } else {
                    // unsigned
                    if (flags & FLAGS_LONG_LONG) {
#if PRINTF_SUPPORT_LONG_LONG
                        idx =
                            print_integer(out, buffer, idx, maxlen,
                                          (printf_unsigned_value_t)va_arg(
                                              va, unsigned long long),
                                          false, base, precision, width, flags);
#endif
                    } else if (flags & FLAGS_LONG) {
                        idx = print_integer(
                            out, buffer, idx, maxlen,
                            (printf_unsigned_value_t)va_arg(va, unsigned long),
                            false, base, precision, width, flags);
                    } else {
                        const unsigned int value =
                            (flags & FLAGS_CHAR)
                                ? (unsigned char)va_arg(va, unsigned int)
                            : (flags & FLAGS_SHORT)
                                ? (unsigned short int)va_arg(va, unsigned int)
                                : va_arg(va, unsigned int);
                        idx =
                            print_integer(out, buffer, idx, maxlen,
                                          (printf_unsigned_value_t)value, false,
                                          base, precision, width, flags);
                    }
                }
                format++;
                break;
            }
#if PRINTF_SUPPORT_DECIMAL_SPECIFIERS
            case 'f':
            case 'F':
                if (*format == 'F')
                    flags |= FLAGS_UPPERCASE;
                idx = print_floating_point(out, buffer, idx, maxlen,
                                           va_arg(va, double), precision, width,
                                           flags, PRINTF_PREFER_DECIMAL);
                format++;
                break;
#endif
#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
            case 'e':
            case 'E':
            case 'g':
            case 'G':
                if ((*format == 'g') || (*format == 'G'))
                    flags |= FLAGS_ADAPT_EXP;
                if ((*format == 'E') || (*format == 'G'))
                    flags |= FLAGS_UPPERCASE;
                idx = print_floating_point(out, buffer, idx, maxlen,
                                           va_arg(va, double), precision, width,
                                           flags, PRINTF_PREFER_EXPONENTIAL);
                format++;
                break;
#endif  // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS
            case 'c': {
                unsigned int l = 1U;
                // pre padding
                if (!(flags & FLAGS_LEFT)) {
                    while (l++ < width) {
                        out(' ', buffer, idx++, maxlen);
                    }
                }
                // char output
                out((char)va_arg(va, int), buffer, idx++, maxlen);
                // post padding
                if (flags & FLAGS_LEFT) {
                    while (l++ < width) {
                        out(' ', buffer, idx++, maxlen);
                    }
                }
                format++;
                break;
            }

            case 's': {
                const char* p = va_arg(va, char*);
                if (p == NULL) {
                    idx = out_rev_(out, buffer, idx, maxlen, ")llun(", 6, width,
                                   flags);
                } else {
                    unsigned int l =
                        strnlen_s_(p, precision ? precision : (size_t)-1);
                    // pre padding
                    if (flags & FLAGS_PRECISION) {
                        l = (l < precision ? l : precision);
                    }
                    if (!(flags & FLAGS_LEFT)) {
                        while (l++ < width) {
                            out(' ', buffer, idx++, maxlen);
                        }
                    }
                    // string output
                    while ((*p != 0) &&
                           (!(flags & FLAGS_PRECISION) || precision--)) {
                        out(*(p++), buffer, idx++, maxlen);
                    }
                    // post padding
                    if (flags & FLAGS_LEFT) {
                        while (l++ < width) {
                            out(' ', buffer, idx++, maxlen);
                        }
                    }
                }
                format++;
                break;
            }

            case 'p': {
                width = sizeof(void*) * 2U +
                        2;  // 2 hex chars per byte + the "0x" prefix
                flags |= FLAGS_ZEROPAD | FLAGS_POINTER;
                uintptr_t value = (uintptr_t)va_arg(va, void*);
                idx = (value == (uintptr_t)NULL)
                          ? out_rev_(out, buffer, idx, maxlen, ")lin(", 5,
                                     width, flags)
                          : print_integer(out, buffer, idx, maxlen,
                                          (printf_unsigned_value_t)value, false,
                                          BASE_HEX, precision, width, flags);
                format++;
                break;
            }

            case '%':
                out('%', buffer, idx++, maxlen);
                format++;
                break;

            default:
                out(*format, buffer, idx++, maxlen);
                format++;
                break;
        }
    }

    // termination
    out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);

    // return written chars without terminating \0
    return (int)idx;
}

/**
 * This function will fill a formatted string to buffer.
 *
 * @param  buf is the buffer to save formatted string.
 *
 * @param  size is the size of buffer.
 *
 * @param  fmt is the format parameters.
 *
 * @param  args is a list of variable parameters.
 *
 * @return The number of characters actually written to buffer.
 */

int pika_platform_vsnprintf(char* buff,
                            size_t size,
                            const char* fmt,
                            va_list args) {
    return __vsnprintf(out_buffer, buff, size, fmt, args);
}
